// Copyright (c) 2022 wick.zt arg-go is licensed under Mulan PSL v2.
// You can use this software according to the terms and conditions of
// the Mulan PSL v2. You may obtain a copy of Mulan PSL v2 at:
// http://license.coscl.org.cn/MulanPSL2
// THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES
// OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
// See the Mulan PSL v2 for more details.

package arg

import (
	"fmt"
	"reflect"
)

const (
	TagKey = "arg"
)

type TriggerFunc func(interface{}) error

const (
	// OrderHighestPriority lifts the arg to the very first of the
	// parsing time.
	OrderHighestPriority = -1024

	// OrderPreferred lifts the Arg before all the general ones.
	OrderPreferred = -512

	// OrderDefault is the default order of the general args.
	OrderDefault = 0
)

type Arg struct {
	name  string
	short byte
	long  string
	usage string
	value reflect.Value
	defv  interface{}
	final bool
	must  bool
	nargs int

	// Order indicates when this Arg is parsed and thus if triggered.
	// This field is mainly used for flags like "Help", "Version", and etc.
	// When two args have the same order, they will be sorted alphabetly.
	Order int

	// Trigger is called when this `Arg` is parsed with `(*Arg).Value()`.
	// The returning error will be handled by the `ArgParser` and thus
	// interrupt the process.
	Trigger TriggerFunc
}

func (a *Arg) Value() interface{} {
	return a.value.Interface()
}

func (a *Arg) Required() bool {
	return a.must
}

// trigger runs the common validators of an `Arg` and then calls the
// `Trigger` function.
func (a *Arg) trigger() error {
	if a.short == 0 && a.long == "" {
		return &ValueError{NullFlag, "null flag", a.name}
	}

	if a.Required() && !a.final {
		return &ValueError{ValueRequired, "arg is required", a.name}
	}

	// the "triggered" judgement will not compare the current value
	// with the default value anymore, but send the value to the
	// TriggerFunc and let the user deal with it instead.
	if a.final && a.Trigger != nil {
		return a.Trigger(a.Value())
	}

	return nil
}

func define(name string, tokens []token, val reflect.Value) (arg *Arg, err error) {
	arg = &Arg{name: name, value: val, nargs: -1}

	var value string
	for _, t := range tokens {
		if t.isShortFlag() {
			arg.short = t[1]
		} else if t.isLongFlag() {
			arg.long = string(t[2:])
		} else if t.isUsage() {
			arg.usage = string(t[1:])
		} else if t.isValue() {
			value = string(t[1 : len(t)-1])
		} else if t.isAttr() {
			if err = arg.setAttr(t[1]); err != nil {
				return nil, err
			}
		} else {
			return nil, &ValueError{UnknownToken, "unknown token", t}
		}
	}

	if arg.Required() && value != "" {
		return nil, &ValueError{MustNotDefault, "required arg must not have a default value", value}
	}

	if arg.nargs == -1 {
		if arg.value.Kind() == reflect.Bool {
			arg.nargs = 0
		} else {
			arg.nargs = 1
		}
	}

	v, err := parseValue(val.Type(), value)
	if err != nil {
		return nil, err
	}
	arg.defv = v
	if v != nil {
		arg.value.Set(reflect.ValueOf(v))
	}
	return arg, nil
}

func (a *Arg) reflectType() reflect.Type {
	return a.value.Type()
}

func (a *Arg) setAttr(token byte) error {
	attr, err := parseAttr(token)
	if err != nil {
		return err
	}
	switch attr {
	case AttrRequired:
		a.must = true
	case AttrZeroArgs, AttrOneArg, AttrMultiArgs:
		if a.nargs != -1 {
			return &ValueError{RedefinedAttr, "redefine nargs", attr}
		}
		a.nargs = int(attr)
	}
	return nil
}

func (a *Arg) setValue(v interface{}) error {
	if a.final {
		return &RuntimeError{"set value twice"}
	}

	s, ok := v.(string)
	if !ok {
		a.value.Set(reflect.ValueOf(v))
		a.final = true
		return nil
	}

	val, err := parseValue(a.reflectType(), s)
	if err != nil {
		return err
	}
	a.value.Set(reflect.ValueOf(val))
	a.final = true

	return nil
}

// Flag holds the tokens for (global) bool value.
type Flag struct {
	Name  string
	Short byte
	Long  string
	Usage string
	flag  bool
}

func NewFlag(name string, short byte, long, usage string, flag bool) *Flag {
	return &Flag{Name: name, Short: short, Long: long, Usage: usage, flag: flag}
}

func (f *Flag) Tag() string {
	if f.flag {
		return fmt.Sprintf("-%c,--%s,<%v>,:%s", f.Short, f.Long, f.flag, f.Usage)
	}
	return fmt.Sprintf("-%c,--%s,:%s", f.Short, f.Long, f.Usage)
}

func (f *Flag) ToArg(order int, trigger TriggerFunc) (*Arg, error) {
	tokens, _ := parseTag(f.Tag())
	arg, err := define(f.Name, tokens, reflect.ValueOf(&f.flag).Elem())
	if err != nil {
		return nil, err
	}
	if order < OrderHighestPriority {
		return nil, fmt.Errorf("invalid order: %d", order)
	}
	arg.Order = order
	arg.Trigger = trigger
	return arg, nil
}

const (
	ShowUsageArgName = "Help"
)

func DefaultUsageFlag() *Flag {
	return &Flag{
		Name:  ShowUsageArgName,
		Short: 'h',
		Long:  "help",
		Usage: "show this message and exit",
		flag:  false,
	}
}
